/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.worldobjects;

import java.util.EnumMap;

import robocup.component.SConf;
import robocup.component.actions.Action;
import robocup.component.actions.KickAction;
import robocup.component.actions.MoveAction;
import robocup.component.actions.TackleAction;
import robocup.component.geometry.Vektor;

/**
 * <code>Ball</code> - a world-model representation of the ball.
 */
public class Ball extends DynamicObject {

	/**
	 * get a dummy instance the easy way.
	 * 
	 * @return a ball with no speed, null-position and no information, when it
	 *         was seen last (-1)
	 */
	public static Ball getDummyInstance() {

		return new Ball(-1, -1, new Vektor(), new Vektor());
	}

	/**
	 * is set true, if a goalie catched the ball.
	 */
	private boolean isCatched = false;

	public enum UPDATE_MODE {
		INIT, EXPECTED, HEARED, SEEN_FULL, SEEN_WITHOUT_SPEED, META_BASED_ON_INFO
	};

	private EnumMap<UPDATE_MODE, Integer> updateCycles = new EnumMap<UPDATE_MODE, Integer>(
			UPDATE_MODE.class);

	/**
	 * Constructs a ball-object with the given values
	 * 
	 * @param iCycle -
	 *            when the ball was perceived last (real cycles)
	 * @param iBodyCycle -
	 *            when the ball was perceived last (in bodyCycles)
	 * @param iPosAbs -
	 *            the absolute ball position
	 * @param iSpeed -
	 *            the speed
	 */
	public Ball(int iCycle, int iBodyCycle, Vektor iPosAbs, Vektor iSpeed) {
		super(iCycle, iBodyCycle, iPosAbs, iSpeed);
	}

	/**
	 * Clones the Ball.
	 * 
	 * @return The Ball clone
	 */
	public Ball cloned() {
		Ball b = Ball.getDummyInstance();
		b.copy(this);
		return b;
	}

	/**
	 * clones a ball without creating new one.
	 * 
	 * @param b
	 */
	public void copy(Ball b) {
		this.setCycle(b.getCycle());
		this.setBodyCycle(b.getBodyCycle());
		this.setPosition(b.getPosition());
		this.setSpeed(b.getSpeed());
		this.setReliable(b.isReliable());
		this.setCatched(b.isCatched());
		this.setSpeedReliable(b.speedIsReliable());
		this.updateCycles = b.updateCycles.clone();
	}

	/**
	 * sets the update mode. this is used to determine when the last update of a
	 * certain update occured.
	 * 
	 * @param mode -
	 *            the update mode
	 * @param bodyCycle -
	 *            the body cycle of the update
	 */
	public void setUpdateMode(UPDATE_MODE mode, int bodyCycle) {
		this.updateCycles.put(mode, new Integer(bodyCycle));
	}

	/**
	 * the age of the ball depending on the update type.
	 * 
	 * @param currentBodyCycle
	 * @param mode
	 * @return the age (wm.getBodyCycle - cycle of last update of given type)
	 */
	public int getAge(int currentBodyCycle, UPDATE_MODE mode) {
		if (mode == UPDATE_MODE.META_BASED_ON_INFO) {
			return Math.min(getAge(currentBodyCycle,
					UPDATE_MODE.SEEN_WITHOUT_SPEED), getAge(currentBodyCycle,
					UPDATE_MODE.HEARED));
		}

		Integer i = this.updateCycles.get(mode);
		if (i == null)
			return Integer.MAX_VALUE;

		if (mode == UPDATE_MODE.INIT) {
			return 0;
		}

		return currentBodyCycle - i.intValue();
	}

	/**
	 * Predicts the effect of an action on the ball.
	 * 
	 * @param p -
	 *            acting player
	 * @param a -
	 *            action
	 * @return a clone of the ball after the action
	 * 
	 * @author rw
	 */
	public Ball predictBallAfterAction(Player p, Action a) {
		Ball b = this.cloned();
		b.predictThisAfterAction(p, a);
		return b;
	}

	/**
	 * predicts the next ball based on an action. YOU ARE WORKING ON A
	 * REFERENCE!!
	 * 
	 * @param p -
	 *            acting player
	 * @param a -
	 *            action
	 * 
	 * @author rw
	 */
	public void predictThisAfterAction(Player p, Action a) {
		// ToDo check collision(rw)
		if (a == null) {
			this.extrapolate(1, SConf.getInstance().BALL_DECAY);
			return;
		}

		switch (a.getType()) {
		case MOVE:
			if (isCatched) {
				MoveAction ma = (MoveAction) a;

				Vektor next = new Vektor(Vektor.XY, ma.getX(), ma.getY());
				next.rotate(90);
				next.y = -next.y;
				this.setPosition(next);
				this.setSpeed(new Vektor(0, 0));
			} else {
				this.extrapolate(1, SConf.getInstance().BALL_DECAY);
			}
			break;

		case KICK:
			KickAction kickAction = (KickAction) a;

			// relative position of ball
			Vektor ballRel = p.getPosRel(this, Player.RELATIVE.TO_BODY);
			double ballDist = Math.abs(ballRel.getLength()
					- SConf.getInstance().BALL_SIZE - p.getPConf().PLAYER_SIZE);
			if (ballDist > p.getPConf().KICKABLE_MARGIN) {
				// it was not possible to kick the ball
				this.extrapolate(1, SConf.getInstance().BALL_DECAY);
				return;
			}
			if (ballDist < 0) {
				ballDist = 0;
			}
			// Equation 3.26 (page 29) from Master Thesis :o)
			double kickedPower = kickAction.getPower()
					* (1.0 - 0.25 * Math.abs(ballRel.getAngle()) / 180.0 - 0.25
							* ballDist / p.getPConf().KICKABLE_MARGIN);
			// Equation 3.27 (page 29) from Master Thesis, ignoring the noise
			Vektor ballAcceleration = new Vektor(Vektor.POLAR, kickedPower
					* SConf.getInstance().KICK_POWER_RATE, kickAction
					.getDirection()
					+ p.getBodyDir());

			if (ballAcceleration.getLength() > SConf.getInstance().BALL_ACC_MAX) {
				ballAcceleration.setLength(SConf.getInstance().BALL_ACC_MAX);
			}

			// calculate new ball speed
			this.speed.addToThis(ballAcceleration);
			if (this.speed.getLength() > SConf.getInstance().BALL_SPEED_MAX) {
				this.speed.setLength(SConf.getInstance().BALL_SPEED_MAX);
			}

			this.position.addToThis(this.speed);
			// ball speed now decays (kick was performed in last cycle)
			this.speed.mult(SConf.getInstance().BALL_DECAY);
			this.isCatched = false;
			return;

		case CATCH_BALL:
			this.isCatched = true;
			// TODO
			// couldn't take this code because
			// it was missing in the BallModel :(
			return;
		case TACKLE:

			TackleAction tackleAction = (TackleAction) a;

			// relative position of ball
			Vektor ballRel1 = p.getPosRel(this, Player.RELATIVE.TO_BODY);
			double ballDist1 = Math.abs(ballRel1.getLength()
					- SConf.getInstance().BALL_SIZE - p.getPConf().PLAYER_SIZE);
			// TODO tackledistance
			if (ballDist1 > 2) {
				// it was not possible to tackle the ball
				this.extrapolate(1, SConf.getInstance().BALL_DECAY);
				return;
			}
			if (ballDist1 < 0) {
				ballDist1 = 0;
			}
			// Equation 3.26 (page 29) from Master Thesis :o)
			double tackledPower = tackleAction.getPower();
			// Equation 3.27 (page 29) from Master Thesis, ignoring the noise
			Vektor ballAcceleration1 = new Vektor(Vektor.POLAR, tackledPower
					* SConf.getInstance().KICK_POWER_RATE, p.getBodyDir());

			if (ballAcceleration1.getLength() > SConf.getInstance().BALL_ACC_MAX) {
				ballAcceleration1.setLength(SConf.getInstance().BALL_ACC_MAX);
			}

			// calculate new ball speed
			this.speed.addToThis(ballAcceleration1);
			if (this.speed.getLength() > SConf.getInstance().BALL_SPEED_MAX) {
				this.speed.setLength(SConf.getInstance().BALL_SPEED_MAX);
			}

			this.position.addToThis(this.speed);
			// ball speed now decays (kick was performed in last cycle)
			this.speed.mult(SConf.getInstance().BALL_DECAY);
			this.isCatched = false;
			return;
		default:
			this.extrapolate(1, SConf.getInstance().BALL_DECAY);
		}
	}

	/**
	 * <code>isCatched</code>
	 * 
	 * @return true if a goalkeeper chatched the ball, else false.
	 */
	public boolean isCatched() {
		return this.isCatched;
	} // end of method isCatched

	/**
	 * <code>setCatched</code>
	 */
	public void setCatched(boolean chatched) {
		this.isCatched = chatched;
	} // end of method setChatched

	@Override
	public String toString() {
		return ("Ball C:" + getCycle() + " BC:" + getBodyCycle() + " Pos:"
				+ getPosition() + " Speed:" + getSpeed() + " Reliable:" + isReliable());
	}
}
